#!/usr/bin/python
import evdev
from evdev import ecodes
import threading
import time
import sys
import math
import os


class Calibration:
    def __init__(self):
        self.calibration_step = 0
        self.calibration_points = [
            (0, 0),
            (-32766 * 0.7, 32767 * 0.7),
            (32766 * 0.7, 32767 * 0.7),
            (32766 * 0.7, -32767 * 0.7),
            (-32766 * 0.7, -32767 * 0.7)
        ]
        self.dev_point = []
        self.minX_dev = 0
        self.minY_dev = 0
        self.maxX_dev = 0
        self.maxY_dev = 0

    def get_calibration_points(self):
        return self.calibration_points[self.calibration_step]

    def clear_calibration_step(self):
        self.calibration_step = 0

    def increment_calibration_step(self):
        self.calibration_step += 1

    def set_dev_value(self, x, y):
        self.dev_point.append((x, y))

    def calculate(self):
        minX_shoot_dev = (self.dev_point[0][0] + self.dev_point[3][0]) / 2
        maxX_shoot_dev = (self.dev_point[1][0] + self.dev_point[2][0]) / 2
        maxY_shoot_dev = (self.dev_point[0][1] + self.dev_point[1][1]) / 2
        minY_shoot_dev = (self.dev_point[2][1] + self.dev_point[3][1]) / 2
        center_dev = [(maxX_shoot_dev + minX_shoot_dev) / 2, (maxY_shoot_dev + minY_shoot_dev) / 2]
        # get module
        self.minX_dev = center_dev[0] - (center_dev[0] - minX_shoot_dev) / 0.7
        self.maxX_dev = center_dev[0] + (maxX_shoot_dev - center_dev[0]) / 0.7
        self.minY_dev = center_dev[1] - (center_dev[1] - minY_shoot_dev) / 0.7
        self.maxY_dev = center_dev[1] + (maxY_shoot_dev - center_dev[1]) / 0.7

    def set_Calibration(self, path):
        cmd1 = "evdev-joystick --e {} -a 0 --minimum {} --maximum {} ".format(path, self.minX_dev, self.maxX_dev)
        cmd2 = "evdev-joystick --e {} -a 1 --minimum {} --maximum {} ".format(path, self.minY_dev, self.maxY_dev)
        os.system(cmd1)
        os.system(cmd2)


class Emulated_gun:
    def __init__(self):
        i = 0
        self.target = None
        self.name = None
        self.path = None
        self.outputSize = [-32766, 32767]  # output rectangle
        self.pLast = [0, 0]
        self.calibrated = 0

    def open(self, name):
        evkeys = []
        for x in range(ecodes.KEY_MAX):
            if x not in [
                ecodes.BTN_DIGI,
                ecodes.BTN_TOOL_PEN,
                ecodes.BTN_TOOL_RUBBER,
                ecodes.BTN_TOOL_BRUSH,
                ecodes.BTN_TOOL_PENCIL,
                ecodes.BTN_TOOL_AIRBRUSH,
                ecodes.BTN_TOOL_FINGER,
                ecodes.BTN_TOOL_MOUSE,
                ecodes.BTN_TOOL_LENS,
                ecodes.BTN_TOUCH,
                ecodes.BTN_STYLUS,
                ecodes.BTN_STYLUS2,
                ecodes.BTN_TOOL_DOUBLETAP,
                ecodes.BTN_TOOL_TRIPLETAP,
                ecodes.BTN_TOOL_QUADTAP]:
                evkeys.append(x)  # avoid some buttons causing issues to libinput

        self.target = evdev.UInput(name=name+" calibrated", events={
            ecodes.EV_ABS: [
                (ecodes.ABS_X, evdev.AbsInfo(value=0, min=-32766, max=32767, fuzz=0, flat=0, resolution=1)),
                (ecodes.ABS_Y, evdev.AbsInfo(value=0, min=-32766, max=32767, fuzz=0, flat=0, resolution=1))
            ],
            ecodes.EV_KEY: evkeys})
        self.path = self.target.device.path

    def close(self):
        self.target.close()

    def set_pointer(self, x, y):
        self.pLast[0] = x
        self.pLast[1] = y
        self.target.write(ecodes.EV_ABS, ecodes.ABS_X, x)
        self.target.write(ecodes.EV_ABS, ecodes.ABS_Y, y)
        self.target.syn()

    def moveTargetTo(self, toposition,poll_gun):
        currentPosition = self.pLast
        x_inc = (toposition[0] - currentPosition[0]) / 100
        y_inc = (toposition[1] - currentPosition[1]) / 100
        i = 0
        while i < 100:
            currentPosition[0] += x_inc
            currentPosition[1] += y_inc
            i += 1
            self.set_pointer(int(currentPosition[0]), int(currentPosition[1]))
            time.sleep(0.01)  # 2 seconds
            poll_gun.read_input()

    def failAnimation(self, originalPosition):
        for i in range(0, 50):
            self.set_pointer(originalPosition[0] + int(20 * math.cos(i)),
                             originalPosition[1] + int(20 * math.sin(i)))
            time.sleep(0.01)  # x1000 = 1 second

    def set_event(self, key, config, value):
        self.target.write(key, config, value)
        self.target.syn()

    def set_fail_event(self):
        self.target.write(ecodes.EV_KEY, ecodes.KEY_CONFIG, 0)
        self.target.syn()


class Gun:
    def __init__(self, device_path, btn_cal):
        self.device = evdev.InputDevice(device_path)
        self.name = self.device.name
        self.x = 0
        self.y = 0
        self.btn_right = 0
        self.btn_left = 0
        self.btn_1 = 0
        self.btn_cal = btn_cal
        self.calibrate = 0
        self.btn_cal_pressed = 0
        self.time1 = 0
        self.event_list = []

    def read_input(self):
        #print("Reading gun input")
        try:
            # Leer todos los eventos presentes en la cola
            for event in self.device.read():
                if event.type == ecodes.EV_ABS:
                    #print("Event ABS")
                    #print("Event code:" + str(event.code))
                    #print("Event val:" + str(event.value))

                    self.x = event.value if event.code == ecodes.ABS_X else self.x
                    self.y = event.value if event.code == ecodes.ABS_Y else self.y
                    self.event_list.append((event.type, event.code, event.value))
                elif event.type == ecodes.EV_KEY:
                    #print("Event key")
                    #print("Event val:" + str(event.code))
                    self.event_list.append((event.type, event.code, event.value))
                    if event.code == self.btn_cal:
                        if event.value == 1:
                            print("Btn_cal pressed")
                            self.btn_cal_pressed = 1
                            self.time1 = time.time()
                        else:
                            print("Btn_cal unpressed")
                            self.btn_cal_pressed = 0
                    if event.code == ecodes.BTN_RIGHT:
                        self.btn_right = event.value
                    if event.code == ecodes.BTN_LEFT:
                        self.btn_left = event.value
                        #if event.value == 1:
                        #    print("BTN_LEFT =1")
                        #else:
                        #    print("BTN_LEFT =0")
                    if event.code == ecodes.BTN_1:
                        self.btn_1 = event.value
                        
            if self.btn_cal_pressed and time.time() - self.time1 > 3:
                #print("CALIBRAR")
                self.calibrate = 1
                self.btn_cal_pressed = 0
        except BlockingIOError:
            pass  # Ignorar la excepción y continuar

        except (OSError, evdev.InputEventError) as e:
            print(f"Error reading joystick: {e}")
            print("Reconnecting...")
            print(f"Error reading joystick: {e}")
            print("Reconnecting...")
            # Intentar reconectar el dispositivo
            try:
                self.device = evdev.InputDevice(self.device.path)
                with open("/tmp/reconnect_error.log", "a") as log_file:
                    log_file.write(f"Reconectando\n")
            except Exception as reconnect_error:
                # Capturar el error de reconexión y guardarlo en un archivo
                print(f"Error reconnecting: {reconnect_error}")
                with open("/tmp/reconnect_error.log", "a") as log_file:
                    log_file.write(f"Error reconnecting: {reconnect_error}\n")
                    
    
    def stop_reading(self):
        pass

    def get_state(self):
        return {
            "x": self.x,
            "y": self.y,
            "btn_right": self.btn_right,
            "btn_trigger": self.btn_left,
            "btn_1": self.btn_1
        }

    def get_event(self):
        if len(self.event_list) != 0:
            val = self.event_list[0]
            del self.event_list[0]
            return val
        else:
            return None

    def set_calibrate(self, value):
        self.calibrate = value

    # Propiedad para acceder a self.calibrate
    def iscalibrate(self):
        return self.calibrate


def main():
    if len(sys.argv) != 2 and len(sys.argv) != 3:
        print("mising device argument")
        exit(1)

    if len(sys.argv) == 3:
        bCalibration = int(sys.argv[2])
    else:
        bCalibration = ecodes.BTN_RIGHT

    # input
    device_path = sys.argv[1]
    print("Dev: " + device_path)
    print("Btn calibration" + str(bCalibration))

    gun = Gun(device_path, bCalibration)


    egun = Emulated_gun()
    egun.open(gun.name)

    cal = Calibration()
    calibration_step = 0
    print("Init while")
    try:
        while True:
            if gun.iscalibrate():
                # button calibration pressed
                # set to center the pointer
                if egun.calibrated == 1:
                    # reset values
                    cal.minX_dev = cal.minY_dev = -32766
                    cal.maxX_dev = cal.maxY_dev = 36767
                    cal.set_Calibration(egun.path)
                    egun.calibrated = 0
                if cal.calibration_step == 0:
                    print("Calibrate")
                    egun.set_event(ecodes.EV_KEY, ecodes.KEY_CONFIG, 1)
                    egun.calibrated = 0
                    # pos to center
                    points = cal.get_calibration_points()
                    egun.moveTargetTo(points,gun)
                    cal.increment_calibration_step()
                gun.read_input()
                g_state = gun.get_state()
                if g_state["btn_trigger"] == 1:
                    #enter to calibration loop
                    print("btn trigger = 1")
                    if cal.calibration_step > 1:
                        print(
                            "Value x:" + str(g_state["x"]) + "Value y:" + str(g_state["y"]) + "calibration_step" + str(
                                cal.calibration_step))
                        x_t = g_state["x"]
                        y_t = g_state["y"]
                        cal.set_dev_value(x_t, y_t)
                    if cal.calibration_step < 5:
                        points = cal.get_calibration_points()
                        egun.moveTargetTo(points,gun)
                    cal.increment_calibration_step()
                    while g_state["btn_trigger"] == 1:
                        gun.read_input()
                        g_state = gun.get_state()
                    print("btn_trigger = 0")
                if cal.calibration_step > 5:
                    print("Calibrated step 5")
                    gun.calibrate = 0
                    cal.calibration_step = 0
                    egun.set_event(ecodes.EV_KEY, ecodes.KEY_CONFIG, 0)
                    time.sleep(1)
                    cal.calculate()
                    cal.set_Calibration(egun.path)
                    egun.calibrated = 1
            else:
                gun.read_input()
                event = gun.get_event()
                if event is not None:
                    egun.set_event(event[0], event[1], event[2])

    except KeyboardInterrupt:
        gun.stop_reading()


if __name__ == "__main__":
    main()


